package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 消息生成器
func msgGen(name string) <-chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			c <- fmt.Sprintf("service %s: message %d", name, i)
			i++
		}
	}()
	return c
}

// 使用第三个channel接收前两个channel的数据，保证同时等待多个channel结果
func fanIn(c1, c2 <-chan string) chan string {
	c := make(chan string)
	go func() {
		for {
			c <- <-c1
		}
	}()
	go func() {
		for {
			c <- <-c2
		}
	}()
	return c
}

func fanIn2(chs ...<-chan string) chan string { // channel合并的方式可以不先确定一个有多少个channel
	c := make(chan string)
	for _, ch := range chs {
		// 方法1：拷贝for循环的变量
		//chCopy := ch
		// 方法2：给func函数传参
		go func(in <-chan string) {
			for {
				//c <- <-chCopy
				c <- <-in
			}
		}(ch)
	}
	return c
}

func fanInBySelect(c1, c2 <-chan string) chan string { // 使用select方式必须要先知道一共有多少个channel
	c := make(chan string)
	go func() {
		for {
			select {
			case m := <-c1:
				c <- m
			case m := <-c2:
				c <- m
			}
		}
	}()
	return c
}

func test() {
	m1 := msgGen("service1") // handle 句柄
	m2 := msgGen("service2")
	//m := fanIn(m1, m2)
	//m := fanInBySelect(m1, m2)
	m3 := msgGen("service3")
	m := fanIn2(m1, m2, m3)
	for {
		//fmt.Println(<-m1) // 这样会造成先收m1，再收m2
		//fmt.Println(<-m2)
		fmt.Println(<-m)
	}
}

func nonBlockingWait(c <-chan string) (string, bool) {
	select {
	case m := <-c:
		return m, true
	default:
		return "", false
	}
}

func test2() {
	m1 := msgGen("service1")
	m2 := msgGen("service2")
	for {
		fmt.Println(<-m1)
		if m, ok := nonBlockingWait(m2); ok {
			fmt.Println(m)
		} else {
			fmt.Println("no message from service2")
		}
	}
}

func timeoutWait(c <-chan string, timeout time.Duration) (string, bool) {
	select {
	case m := <-c:
		return m, true
	case <-time.After(timeout):
		return "", false
	}
}

func test3() {
	m1 := msgGen("service1")
	for {
		if m, ok := timeoutWait(m1, time.Second); ok {
			fmt.Println(m)
		} else {
			fmt.Println("timeout")
		}
	}
}

// 通知goroutine退出
func msgGen1(name string, done chan struct{}) chan string {
	c := make(chan string)
	go func() {
		i := 0
		for {
			select {
			case <-time.After(time.Duration(rand.Intn(2000)) * time.Millisecond):
				c <- fmt.Sprintf("service %s: message %d", name, i)
			case <-done:
				fmt.Println("cleaning up")
				time.Sleep(2 * time.Second)
				fmt.Println("cleanup done")
				done <- struct{}{} // 将done用作双向channel，告诉外面已关闭，优雅退出
				return
			}
			i++
		}
	}()
	return c
}

func test4() {
	done := make(chan struct{})
	m1 := msgGen1("service1", done)
	for i := 0; i < 5; i++ {
		if m, ok := timeoutWait(m1, time.Second); ok {
			fmt.Println(m)
		} else {
			fmt.Println("timeout")
		}
	}
	done <- struct{}{}
	//time.Sleep(time.Second)
	<-done
}

func main() {
	//test()
	//test2()
	//test3()
	test4()
}
